#include "network/PacketReaderWriter.h"

#include "network/packet/Packets.h"

#include <QBuffer>
#include <QDebug>

PacketReaderWriter::PacketReaderWriter() {
    xmlWriter.setAutoFormatting(true);
}


void PacketReaderWriter::writePacketData(const AbstractPacket * packet, QByteArray & array) {
    QBuffer buff(&array);
    buff.open(QIODevice::WriteOnly);
    xmlWriter.setDevice(&buff);

    QString packetType = QString("%1").arg(packet->getPacketType());

    xmlWriter.writeStartDocument();
    xmlWriter.writeStartElement("packet");
    xmlWriter.writeAttribute("type", packetType);
    xmlWriter.writeAttribute("senderId", packet->getSenderId());

    writePacket(packet);

    xmlWriter.writeEndElement();
    xmlWriter.writeEndDocument();
}

AbstractPacket * PacketReaderWriter::readPacket(QByteArray & array) {
    QBuffer buff(&array);
    buff.open(QIODevice::ReadOnly);
    xmlReader.setDevice(&buff);

    AbstractPacket *packet = NULL;

    if (xmlReader.readNextStartElement() && xmlReader.name() == "packet") {
        QXmlStreamAttributes attributes = xmlReader.attributes();
        QString typeStr = attributes.value("type").toString();
        QString senderId = attributes.value("senderId").toString();
        PacketType packetType = getPacketType(typeStr);
        packet = readPacket(packetType);
        if (packet != NULL) {
            packet->setSenderId(senderId);
        }
    }

    return packet;
}

void PacketReaderWriter::writePacket(const AbstractPacket * packet) {
    switch (packet->getPacketType()) {
    case JOIN_REQUEST:
    {
        JoinRequestPacket *joinRequestPacket = (JoinRequestPacket *) packet;
        xmlWriter.writeTextElement("senderAddress", joinRequestPacket->getSenderAddress());
        xmlWriter.writeTextElement("senderPort", formatString(joinRequestPacket->getSenderPort()));
        writeColor(joinRequestPacket->getPenColor());
        return;
    }
    case JOIN_RESPONSE:
    {
        JoinResponsePacket *joinResponsePacket = (JoinResponsePacket *) packet;
        xmlWriter.writeTextElement("width", formatString(joinResponsePacket->getWidth()));
        xmlWriter.writeTextElement("height", formatString(joinResponsePacket->getHeight()));
        writeImage(joinResponsePacket->getImage());
        QList<Node> * nodeList = joinResponsePacket->getNodeList();
        for (QList<Node>::iterator it = nodeList->begin(); it != nodeList->end(); ++it) {
            writeNode(*it);
        }
        return;
    }
    case JOIN_INFO:
    {
        JoinInfoPacket *joinInfoPacket = (JoinInfoPacket *) packet;
        writeNode(*joinInfoPacket->getJoinNode(), "joinNode");
        writeNode(*joinInfoPacket->getPrevJoinNode(), "prevNode");
        return;
    }
    case PAINT:
    {
        PaintPacket *paintPacket = (PaintPacket *) packet;
        const QList<DrawPath *> * paths = paintPacket->getDrawPaths();
        for (int i = 0; i < paths->length(); ++i) {
            DrawPath * path = paths->at(i);
            xmlWriter.writeStartElement("path");
            for (int j = 0; j < path->getPointList().length(); ++j) {
                QPoint point = path->getPointList()[j];
                xmlWriter.writeStartElement("point");
                xmlWriter.writeAttribute("x", formatString(point.x()));
                xmlWriter.writeAttribute("y", formatString(point.y()));
                xmlWriter.writeEndElement();
            }
            xmlWriter.writeEndElement();
        }
        return;
    }
    case MUTUAL_EXCL_TOKEN:
    {
        MutualExclusionTokenPacket * mutualExclusionTokenPacket = (MutualExclusionTokenPacket *) packet;
        const MutualExclusionToken & token = mutualExclusionTokenPacket->getToken();
        xmlWriter.writeStartElement("token");
        xmlWriter.writeAttribute("locked", token.isLocked() ? "true" : "false");
        if (token.isLocked()) {
            qDebug() << "OwnerId " << token.getOwnerId();
            xmlWriter.writeAttribute("ownerId", token.getOwnerId());
        }
        xmlWriter.writeEndElement();
        return;
    }
    case BUSY:
        break;
    }   
}

AbstractPacket * PacketReaderWriter::readPacket(PacketType packetType) {
    AbstractPacket *packet = NULL;
    switch (packetType) {
    case JOIN_REQUEST:
    {
        QString * senderAddress = readTextElement("senderAddress", "JoinRequestPacket read error");
        QString * senderPortStr = readTextElement("senderPort", "JoinRequestPacket read error");
        QColor color = readColor();


        if (senderAddress != NULL && senderPortStr != NULL) {
            quint16 senderPort = senderPortStr->toUInt();
            packet = new JoinRequestPacket(*senderAddress, senderPort, color);
        }

        deletePtr(senderAddress);
        deletePtr(senderPortStr);
    }
    break;
    case JOIN_RESPONSE:
    {
        QString * widthStr = readTextElement("width", "JoinInfoPacket read error");
        QString * heightStr = readTextElement("height", "JoinInfoPacket read error");
        QImage image;
        readImage(image);

        if (widthStr != NULL && heightStr != NULL) {
            Node * node = NULL;
            QList<Node> nodeList;
            while ((node = readNode("node", "JoinResponsePacket read error")) != NULL) {
                nodeList.append(*node);
                delete node;
            }
            packet = new JoinResponsePacket(nodeList, widthStr->toUInt(), heightStr->toUInt(), image);
        }

        deletePtr(widthStr);
        deletePtr(heightStr);
    }
    break;
    case JOIN_INFO:
    {
        Node * joinNode = readNode("joinNode", "JoinInfoPacket read error");
        Node * prevNode = readNode("prevNode", "JoinInfoPacket read error");

        if (prevNode != NULL && joinNode != NULL) {
            packet = new JoinInfoPacket(*joinNode, *prevNode);
        }

        deletePtr(prevNode);
        deletePtr(joinNode);
    }
    break;
    case PAINT:
    {
        QList<DrawPath *> * paths = new QList<DrawPath *>();

        DrawPath * path = NULL;
        while (xmlReader.readNext() && !xmlReader.hasError()) {
            if (xmlReader.isStartElement() && xmlReader.name() == "path") {
                path = new DrawPath();
                paths->append(path);
            }

            if (xmlReader.isStartElement() && xmlReader.name() == "point") {
                int x = xmlReader.attributes().value("x").toString().toInt();
                int y = xmlReader.attributes().value("y").toString().toInt();
                path->addPoint(QPoint(x, y));
            }
        }

        packet = new PaintPacket(*paths);
        delete paths;
    }
    break;
    case MUTUAL_EXCL_TOKEN:
    {
        qDebug() << xmlReader.name();
        while (xmlReader.readNext() && !xmlReader.hasError()) {
            if (xmlReader.name() != "token") {
                continue;
            }

            QString lockedString = xmlReader.attributes().value("locked").toString();
            bool locked = lockedString == "true";
            QString ownerId;
            if (locked) {
                ownerId = xmlReader.attributes().value("ownerId").toString();
            }
            MutualExclusionToken token(ownerId);
            packet = new MutualExclusionTokenPacket(token);
            break;
        }
    }
    break;
    case BUSY:
        packet = new BusyPacket();
        break;
    }
    return packet;
}

void PacketReaderWriter::writeNode(const Node & node, const char * nodeElementName) {
    xmlWriter.writeStartElement(nodeElementName);
    xmlWriter.writeAttribute("id", node.getId());
    xmlWriter.writeTextElement("address", node.getAddress());
    xmlWriter.writeTextElement("port", formatString(node.getPort()));
    writeColor(node.getPaintColor());
    xmlWriter.writeEndElement();
}

QString PacketReaderWriter::formatString(quint16 value) {
    return QString("%1").arg(value);
}

PacketType PacketReaderWriter::getPacketType(const QString & packetTypeString) {
    return (PacketType) packetTypeString.toInt();
}

QString * PacketReaderWriter::readTextElement(const QString & name, const QString & /*errorMsg*/) {
    while (xmlReader.readNext() && !xmlReader.hasError()) {
        if (xmlReader.isStartElement() && xmlReader.name() == name) {
            return new QString(xmlReader.readElementText());
        }
    }
    return NULL;
}

Node * PacketReaderWriter::readNode(const QString & name, const QString & errorMsg) {
    Node * node = NULL;
    while (xmlReader.readNext() && !xmlReader.hasError()) {
        if (!xmlReader.isStartElement() || xmlReader.name() != name) {
            continue;
        }

        QString id = xmlReader.attributes().value("id").toString();
        QString *address = readTextElement("address", errorMsg);
        QString *portStr = readTextElement("port", errorMsg);
        QColor color = readColor();

        if (address != NULL && portStr != NULL) {
            quint16 port = portStr->toUInt();
            node = new Node(id, *address, port, color);
        }

        deletePtr(address);
        deletePtr(portStr);
        break;
    }
    return node;
}

QColor PacketReaderWriter::readColor() {
    int r = -1, g = -1, b = -1;
    while (xmlReader.readNext() && !xmlReader.hasError()) {
        if ( xmlReader.name() == "color") {
            r = xmlReader.attributes().value("r").toString().toInt();
            g = xmlReader.attributes().value("g").toString().toInt();
            b = xmlReader.attributes().value("b").toString().toInt();
            break;
        }
    }
    qDebug() << "r=" << r << " g=" << g << " b=" << b;
    return QColor(r, g, b);
}

void PacketReaderWriter::writeColor(QColor color) {
    xmlWriter.writeStartElement("color");
    xmlWriter.writeAttribute("r", formatString(color.red()));
    xmlWriter.writeAttribute("g", formatString(color.green()));
    xmlWriter.writeAttribute("b", formatString(color.blue()));
    xmlWriter.writeEndElement();
}

void PacketReaderWriter::writeImage(const QImage & image) {
    QByteArray byteArray;
    QBuffer imageBuff(&byteArray);
    imageBuff.open(QIODevice::ReadWrite);
    image.save(&imageBuff, "png");
    xmlWriter.writeStartElement("image");
    QByteArray arr = byteArray.toBase64();
    xmlWriter.writeCDATA(QString(arr).toUtf8());
    xmlWriter.writeEndElement();
}

void PacketReaderWriter::readImage(QImage & image) {
    while (xmlReader.readNext() && !xmlReader.hasError()) {
        if (xmlReader.isStartElement() && xmlReader.name() == "image") {
            QString imageStr = xmlReader.readElementText();
            QByteArray arr = QByteArray::fromBase64(imageStr.toUtf8());
            image.loadFromData(arr, "png");
            break;
        }
    }
}

void PacketReaderWriter::riseReadError(const QString & errorMsg) {
    qCritical() << errorMsg;
    xmlReader.raiseError(errorMsg);
}

template <typename T> void PacketReaderWriter::deletePtr(T * ptr) {
    if (ptr != NULL) {
        delete ptr;
    }
}
